package org.senseframework.support.lucene.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by lanjian
 */
public class Searcher {

    private Integer page = 1;

    private Integer limit = 10;

    private Integer maxPage = 100;

    private DocumentDefinition documentDefinition;

    public Searcher(DocumentDefinition documentDefinition) {
        this.documentDefinition = documentDefinition;
    }

    public static Searcher build(Class clazz) {
        DocumentDefinition documentDefinition = DocumentContext.findDocumentDefinition(clazz);
        return new Searcher(documentDefinition);
    }


    private List<SortField> sortFields = new ArrayList<SortField>();

    private BooleanQuery query = new BooleanQuery();

    private Analyzer analyzer = new CJKAnalyzer();


    public Searcher page(Integer page) {
        this.page = page;
        return this;
    }

    public Searcher limit(Integer limit) {
        this.limit = limit;
        return this;
    }


    public Searcher all() {
        MatchAllDocsQuery all = new MatchAllDocsQuery();
        query.add(all, BooleanClause.Occur.SHOULD);
        return this;
    }

    //BooleanClause.Occur.MUST表示and,BooleanClause.Occur.MUST_NOT表示not,BooleanClause.Occur.SHOULD表示or.
    public Searcher must(String keyword, String field) {
        try {
            QueryParser parser = new QueryParser(field, analyzer);
            query.add(parser.parse(keyword), BooleanClause.Occur.MUST);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return this;
    }

    public Searcher not(String keyword, String field) {
        try {
            QueryParser parser = new QueryParser(field, analyzer);
            query.add(parser.parse(keyword), BooleanClause.Occur.MUST_NOT);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return this;
    }

    public Searcher termMust(String field, String... value) {
        for (String s : value) {
            query.add(new TermQuery(new Term(field, s)), BooleanClause.Occur.MUST);
        }
        return this;
    }

    public Searcher termShould(String field, String... value) {
        for (String s : value) {
            query.add(new TermQuery(new Term(field, s)), BooleanClause.Occur.SHOULD);
        }
        return this;
    }


    public Searcher range(String filed, Integer min, Integer max) {
        query.add(NumericRangeQuery.newIntRange(filed, min, max, true, true), BooleanClause.Occur.MUST);
        return this;
    }


    public Searcher fuzzy(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new FuzzyQuery(new Term(s, keyword)), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.SHOULD);
        return this;
    }

    public Searcher fuzzyMust(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new FuzzyQuery(new Term(s, keyword)), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.MUST);
        return this;
    }

    public Searcher prefix(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new PrefixQuery(new Term(s, keyword)), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.SHOULD);
        return this;
    }

    public Searcher prefixMust(String keyword, FieldInfo... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (FieldInfo s : field) {
            PrefixQuery prefixQuery = new PrefixQuery(new Term(s.getField(), keyword));
            prefixQuery.setBoost(s.getBoost());
            booleanQuery.add(prefixQuery, BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.MUST);
        return this;
    }

    public Searcher must(FieldInfo... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (FieldInfo s : field) {
            TermQuery termQuery = new TermQuery(new Term(s.getField(), s.getKeyword()));
            termQuery.setBoost(s.getBoost());
            booleanQuery.add(termQuery, BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.MUST);
        return this;
    }


    public Searcher prefixMust(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new PrefixQuery(new Term(s, keyword)), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.MUST);
        return this;
    }

    public Searcher wildcard(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new WildcardQuery(new Term(s, "*" + keyword + "*")), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.SHOULD);
        return this;
    }

    public Searcher wildcardMust(String keyword, String... field) {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (String s : field) {
            booleanQuery.add(new WildcardQuery(new Term(s, "*" + keyword + "*")), BooleanClause.Occur.SHOULD);
        }
        query.add(booleanQuery, BooleanClause.Occur.MUST);
        return this;
    }

    public Searcher should(String keyword, String field) {
        try {
            QueryParser parser = new QueryParser(field, analyzer);
            query.add(parser.parse(keyword), BooleanClause.Occur.SHOULD);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return this;
    }


    public Searcher asc(SortField.Type type, String... fields) {
        for (String field : fields) {
            sortFields.add(new SortField(field, type, false));
        }
        return this;
    }

    public Searcher asc(FieldComparatorSource fieldComparatorSource, String... fields) {
        for (String field : fields) {
            sortFields.add(new SortField(field, fieldComparatorSource, false));
        }
        return this;
    }


    public Searcher desc(FieldComparatorSource fieldComparatorSource, String... fields) {
        for (String field : fields) {
            sortFields.add(new SortField(field, fieldComparatorSource, true));
        }
        return this;
    }

    public Searcher desc(SortField.Type type, String... fields) {
        for (String field : fields) {
            sortFields.add(new SortField(field, type, true));
        }
        return this;
    }

    private static Log log = LogFactory.getLog(Searcher.class);

    private Filter filter = null;

    public Searcher filter(Filter filter) {
        this.filter = filter;
        return this;
    }

    public QueryResult search() {
        return documentDefinition.search(new SearcherHandler() {
            public QueryResult handler(IndexSearcher searcher) {
                try {
                    log.info("query:" + query.toString());
                    TopDocs topDocs = null;
                    if (sortFields.isEmpty()) {
                        topDocs = searcher.search(query, filter, maxPage * limit);// 只返回前maxPage * limit条记录
                    } else {
                        Sort sort = new Sort(sortFields.toArray(new SortField[sortFields.size()]));
                        topDocs = searcher.search(query, filter, maxPage * limit, sort, true, true);// 只返回前maxPage * limit条记录
                    }
                    int total = topDocs.totalHits > maxPage * limit ? maxPage * limit : topDocs.totalHits; // 搜索结果总数量
                    ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 搜索返回的结果集合
                    int totalPage = ((total - 1) / limit) + 1;
                    if (page > totalPage) {
                        return new QueryResult(0, 1, limit, new ArrayList());
                    }
                    int begin = (page - 1) * limit > total ? 0 : (page - 1) * limit;
                    int end = page * limit > total ? total : page * limit;
                    List list = new ArrayList();
                    for (int i = begin; i < end; i++) {
                        ScoreDoc scoreDoc = scoreDocs[i];
                        Document doc = searcher.doc(scoreDoc.doc);
                        log.info("doc:" + scoreDoc.doc + ",score:" + scoreDoc.score + ",boost:" + doc.get("boost"));
                        list.add(documentDefinition.documentToObj(doc));
                    }
                    return new QueryResult(total, page, limit, list);
                } catch (Exception e) {
                    log.error("searcher query", e);
                }
                return new QueryResult(0, 1, limit, new ArrayList());
            }
        });
    }

}
